<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8">
    <title>Sucha's Blog - Archive for September, 2017</title>
    <meta name="generator" content="MarkdownProjectCompositor.lua">
    <meta name="author" content="Sucha">
    <meta name="keywords" content="suchang, programming, Linux, Lua">
    <meta name="description" content="Sucha's blog">
    <link rel="shortcut icon" href="../images/ico.png">
    <link rel="stylesheet" type="text/css" href="../styles/blog.css">
    <link rel="stylesheet" type="text/css" href="../styles/prism.min.css">
    <style id="site_theme"></style>
  </head>
  <body>
    <div id="body">
      <div id="text">
	   <!-- Page published by cmark-gfm begins here --><h1>Sucha's Blog ~ Archive for September, 2017</h1>
<p><a id="p0"></a></p>
<div class="date">17年9月5日 周二 00:59</div>
<h2>有关 m_net 跨平台的小型网络库</h2>
<p>最近一直在玩 <a href="https://github.com/lalawue/m_net">m_net</a>，当然总是处于修修改改当中。</p>
<p>很早就有参考 <a href="https://github.com/rxi/dyad">dyad</a> 这个优雅到不行的网络库，C89 的语法，接口设计清晰明了；
仅针对 TCP 抽象为 Stream；对于 socket 的状态变化，设置为事件监听接收处理；
简单粗暴的 SDK 接收数据再发通知，buffer 地址以及数据 size 直接送到用户手
上，接收部分则随着数据增大，而不断 realloc 出来；读写接口还加上了方便的
基于 C string 的处理。</p>
<p>基于这一套网络库构造的 demo，简明得不行，比如首页的 echo server。</p>
<p>且后者还提供了 timer。这个 timer 的实现，其实基于 select 对于阻塞 socket
的监听。</p>
<p>m_net 历史，最早是从 13 年做游戏时弄的一个单独处理 socket 的接口，当时写
得很挫先不说。后来慢慢发展，看到了 dyad 后，大量参考了 dyad 的设计。比如
抽象了 win 下的接口，使用 select 处理三个平台的 socket 事件，不同的是，
一开始就包含了 TCP、UDP 的支持，一开始就是非阻塞的接口，对于 socket 则命
名为 channel，简称 chann。</p>
<p>但后来 m_net 在不同平台采用了 kqueue、epoll 的实现，接口以及处理上就很不
同了，代码量也增加了很多。状态关注点少了不少，从事件数量上就可以看出来。</p>
<p>最近 m_net 的更改，是将 C 这一层的接口整理了一下，增加了两个 utils。一个
是用于 resolve host 的函数处理 host name 到 ip 的转化（DNS）；另外是单独
对于 ip:port 这样的字串，加入一个 parse 的接口，分割拿到 ip 跟 port。</p>
<p>把 C 的这一层命名为 core 之后，在 C 接口的基础上，抽象了 C++ 的 wrapper
对象，有三种 ChannAddr，Chann 和 ChannDispacher。</p>
<p>分别用来处理地址部分，chann 的数据、状态处理，以及所有 chann 的事件监听、
分发。抽象的结果，是在 C++ 这一层，比如针对 client、server 的处理，可以
类似 C 一样设置一个事件函数，或者继承 Chann 父类，封装专门处理 client、
server 的子类，所有的事件、数据都在子类的内部流转，容易理解，易看易懂。</p>
<p>C++ 支持了闭包后，单纯的 listen 接口不需要外建函数，或者派生子类，只需构
造一个闭包处理就好，方便了很多。</p>
<p>但是跟 dyad 还是有很大差距的，巨大的差距，在于对于数据的处理，m_net 不会
在 recv 事件（数据到达）的时候帮用户接收数据，所以用户任何时候去 recv 数
据，都需要自己准备一个 buffer，跟平常的 socket recv、send 没有什么不同。</p>
<p>这个代码量就出来了，且 buffer 处理的优雅与否，比如针对不同的 protocol 抽
象，是巨大的考验，我目前没有好的想法。</p>
<p>暂时是不想跟 dyad 一样帮用户处理了，我觉得这样也不好，比如不断读取然后
append 到已有的 buffer 上，大小不够了向系统要，不断 realloc 也很颓。</p>
<p>m_net 没有对 recv 先接收处理，但是对于 send 部分，如果 socket 只是反馈
would block，而不是其他的不可恢复的 error，则 SDK 会构造 buffer 存入用户
数据，待 socket 可写后再慢慢 drain 数据。</p>
<p>目前没有设置阈值，上层可以一直写，一直到内存耗尽，不知道底层其实 socket
虽未断开但已经无法写入很久很久了。</p>
<p>可以考虑设置某个阈值，比如 16M 的时候，给上层一个 EAGAIN 之类的，目前先
不管。</p>
<p>另外重要 feature，比如 timer 部分目前是没有的，由于所有 socket 都是非阻
塞，如果仅仅在事件循环的时候获取当前时间，并走一次 timer chain，极端情况
下，CPU 时间片占用是很可观的，这也是我一开始避免加入 timer 的原因。</p>
<p>如果要加入 timer，那在这种情况下，我觉得，需要定一个最小的时间分割，一次
循环，不多不少，就走一个 timer circle，比如 10ms，每 10ms 获取一次事件，
或者多次循环获取一次，然后矫正，这个问题不大。</p>
<p>这种方案下，CPU 时间片占用方面才可以接受，上层事件的精确度也能够保证。</p>
<p>只是目前 timer 部分接口，如果 mnet_poll() 上面增加调用，感觉也不不是很好，
毕竟很多时候，是不需要这个 timer 的。目前一个 -1 的值设过去，有事件就突
击处理一下，没事件就死等待好了。</p>
<p>所以 timer 部分的接口，是集成在 mnet_poll() 之上，还是给用户一个选择，单
独的设置并处理，还需要斟酌一下。</p>
<div class="category"><a href="CategoryProgramming.html">CategoryProgramming</a> / <a href="2017-09.html#p0">Permalink</a> / <a href="https://github.com/lalawue/homepage/discussions/categories/blog" target="_blank">Discussion</a></div>
<!-- date: 2017-09-05T00:59:18+0800 -->
<!-- Page published by cmark-gfm ends here -->
  <div id="foot">2004-<script>var d = new
	Date();document.write(d.getFullYear())</script> &copy;
	Sucha. Powered by MarkdownProjectCompositor.
  </div>
  </div><!-- text -->
  <div id="sidebar">
  </div><!-- sidebar -->
  <script src="../js/prism.min.js" async="async"></script>
  <script src="../js/blog_sidebar.js"></script>
  </div> <!-- body -->
</body>
</html>